﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

using Nui = Microsoft.Research.Kinect.Nui;
using Microsoft.Research.Kinect.Nui;
using System.Windows.Shapes;
using System.Windows.Controls;
using MSUS.Kinect.SkeletonTracker.Utils;
using System.IO;
using System.Runtime.Serialization;

namespace MSUS.Kinect.SkeletonTracker
{
	/// <summary>
	/// Records the history for a particular joint.  Also used to record Gestures.
	/// </summary>
	[DataContract]
	public class JointPositionHistory
	{
		[DataMember]
		public JointID JointId { get; set; }
		[DataMember]
		public string GestureName { get; set; }
		[DataMember]
		public int HistoricNodesLength { get; set; }
		[DataMember]
		protected List<Nui.Vector> _positionHistory;
		/// <summary>
		/// The ShoulderCenter joint vector, for point of reference.
		/// </summary>
		[DataMember]
		public Nui.Vector NeckVector { get; set; }


		/// <summary>
		/// a simple Get for external code to access the history
		/// </summary>
		public List<Nui.Vector> PositionNodes
		{
			get
			{
				return _positionHistory;
			}
		}


		public Nui.Vector CurrentPosition
		{
			get
			{
				if (_positionHistory.Count == 0)
				{
					return new Nui.Vector();	//	just return a blank vector
				}
				else
				{
					return _positionHistory[0];
				}
			}
		}

		public JointPositionHistory(JointID jointId, string jointName, int historicNodesLength)
		{
			HistoricNodesLength = historicNodesLength;	//	default
			JointId = jointId;
			GestureName = jointName;
			_positionHistory = new List<Nui.Vector>(HistoricNodesLength);
		}


		/// <summary>
		/// records a new point for the Joint
		/// also pops the oldest node from the history list
		/// </summary>
		/// <param name="position"></param>
		public void RecordPosition(Nui.Vector position)
		{
			_positionHistory.Insert(0, position);
			//	pop the last item if the list is full
			if (_positionHistory.Count > HistoricNodesLength)
				_positionHistory.Remove(_positionHistory.Last());
		}

		#region Serialize/Deserialize

		/// <summary>
		/// stores a Gesture to a named file
		/// </summary>
		/// <param name="gesturePath"></param>
		/// <param name="neckVector"></param>
		/// <param name="jointNameOverride"></param>
		public void StorePositionHistory(string gesturePath, Nui.Vector neckVector, string jointNameOverride = "")
		{
			using (StreamWriter sr = new StreamWriter(gesturePath))
			{
				StorePositionHistory(sr, neckVector, jointNameOverride);
			}
		}

		/// <summary>
		/// Stores a Gesture to a FileStream
		/// </summary>
		/// <param name="stream"></param>
		/// <param name="neckVector"></param>
		/// <param name="jointNameOverride"></param>
		public void StorePositionHistory(StreamWriter stream, Nui.Vector neckVector, string jointNameOverride = "")
		{
			stream.WriteLine("JointPositionHistory");
			
			//	store the neck position for the central point of reference.
			NeckVector = neckVector;

			string jointNameTemp = GestureName;
			if (!string.IsNullOrEmpty(jointNameOverride))
				GestureName = jointNameOverride;
			string json = Utils.JsonSerializer<JointPositionHistory>.ToJson(this);
			GestureName = jointNameTemp;
			stream.WriteLine(json);
		}

		/// <summary>
		/// Loads a gesture from a named file
		/// </summary>
		/// <param name="gesturePath"></param>
		/// <returns></returns>
		public static JointPositionHistory LoadPositionHistory(string gesturePath)
		{
			JointPositionHistory gesture =  null;

			if (File.Exists(gesturePath))
			{
				using (StreamReader sr = new StreamReader(gesturePath))
				{
					gesture = JointPositionHistory.LoadPositionHistory(sr);
				}
			}
			return gesture;
		}


		/// <summary>
		/// loads a gesture from a FileStream
		/// </summary>
		/// <param name="stream"></param>
		/// <returns></returns>
		public static JointPositionHistory LoadPositionHistory(StreamReader stream)
		{
			string json = stream.ReadLine();
			if (json == "JointPositionHistory")
			{
				//	ignore the header text
				json = stream.ReadToEnd();
			}
			JointPositionHistory history = Utils.JsonSerializer<JointPositionHistory>.FromJson(json);
			//	the historyLength may be custom, so re-set it.
			history.HistoricNodesLength = history._positionHistory.Count;
			return history;
		}

		#endregion

		/// <summary>
		/// Compares two gestures
		/// </summary>
		/// <param name="gesture"></param>
		/// <param name="neckVector"></param>
		/// <param name="tolerance"></param>
		/// <returns>Returns true if they appear to be a match</returns>
		public bool CompareForGesture(JointPositionHistory gesture, Nui.Vector neckVector, float tolerance)
		{
			//	need to have a minimum amount of nodes
			if (gesture._positionHistory.Count < 5 )
				return false;

			Nui.Vector thisCentre = neckVector;
			Nui.Vector thatCentre = gesture.NeckVector;

			for (int i = 0; i < gesture.HistoricNodesLength; i++)
			{
				//	check for out of bounds
				if (_positionHistory.Count <= i || gesture._positionHistory.Count <= i)
					return false;

				Nui.Vector thisNode = _positionHistory[i];
				Nui.Vector thatNode = gesture._positionHistory[i];

				float variance;
				//	check X coord
				variance = (thisNode.X - thisCentre.X) - (thatNode.X - thatCentre.X);
				if (variance < 0)
					variance = -variance;
				if (variance > tolerance)
					return false;

				//	check Y coord
				variance = (thisNode.Y - thisCentre.Y) - (thatNode.Y - thatCentre.Y);
				if (variance < 0)
					variance = -variance;
				if (variance > tolerance)
					return false;

				//	check Z coord
				variance = (thisNode.Z - thisCentre.Z) - (thatNode.Z - thatCentre.Z);
				if (variance < 0)
					variance = -variance;
				if (variance > tolerance)
					return false;
			}

			//	if we get to the end, then it's a match!
			return true;
		}

		/// <summary>
		/// Clone the Gesture
		/// </summary>
		/// <returns></returns>
		public JointPositionHistory Clone()
		{
			JointPositionHistory clone = new JointPositionHistory(JointId, GestureName, HistoricNodesLength)
			{
				 NeckVector = NeckVector,
			};
			foreach (Nui.Vector vector in PositionNodes)
			{
				clone.PositionNodes.Add(new Nui.Vector() { X = vector.X, Y = vector.Y, Z = vector.Z, W = vector.W });
			}
			return clone;
		}



		#region Canvas Rendering code

		/// <summary>
		/// Renders a series of linked lines within a canvasElement.
		/// it looks for a line element named "[JointName/GestureName]JointLine0" as the starting point of the line.
		/// Used by the Gesture Runner
		/// Requires a parent Canvas element to draw the lines within.
		/// </summary>
		/// <param name="parentElement"></param>
		public void RenderPath(Canvas parentCanvas, int screenWidth, int screenHeight, bool renderHistory = false)
		{
			//	only continue if we have any historical points loaded.
			if (_positionHistory.Count > 0)
			{
				//	get the first element.
				// this will tell us the color, etc we need to use for later elements
				Line line0 = GetChildElement(GetLineElementName(GestureName, 0), parentCanvas) as Line;

				//	don't continue if we don't have a line0 specified in the xaml
				if (line0 != null)
				{
					Nui.Vector position0 = _positionHistory[0].ScaleToScreen(screenWidth, screenHeight);
					line0.X1 = position0.X;
					line0.Y1 = position0.Y;
					line0.X2 = position0.X;
					line0.Y2 = position0.Y;

					if (renderHistory)
					{
						Line lastLine = line0;
						Line thisLine = null;
						int i = 0;
						bool skipper = true;
						foreach (Nui.Vector positionItemNode in _positionHistory)
						{
							Nui.Vector scaledVector = positionItemNode.ScaleToScreen(screenWidth, screenHeight);
							i++;
							//	skip the first item
							if (skipper)
							{
								skipper = false;
								continue;
							}

							string lineElementName = GetLineElementName(GestureName, i);
							thisLine = GetChildElement(lineElementName, parentCanvas) as Line;
							if (thisLine == null)
							{
								//create it
								thisLine = new Line();
								thisLine.Name = lineElementName;
								thisLine.Stroke = line0.Stroke;
								thisLine.StrokeThickness = line0.StrokeThickness;
								parentCanvas.Children.Add(thisLine);
							}

							thisLine.X2 = scaledVector.X;
							thisLine.Y2 = scaledVector.Y;
							thisLine.X1 = lastLine.X2;
							thisLine.Y1 = lastLine.Y2;

							lastLine = thisLine;
						}
					}
				}
			}
		}

		/// <summary>
		/// Renders a series of dots within a canvasElement.
		/// Used by the Gesture Editor
		/// </summary>
		/// <param name="parentElement"></param>
		public void RenderPoints(Canvas parentCanvas, int screenWidth, int screenHeight, int highlightNodeNum)
		{
			ClearRenderedPoints(parentCanvas);

			//	only continue if we have any historical points loaded.
			if (_positionHistory.Count > 0)
			{
				int i = 0;
				foreach (Nui.Vector positionItemNode in _positionHistory)
				{
					Nui.Vector scaledVector = positionItemNode.ScaleToScreen(screenWidth, screenHeight);

					string pointElementName = GetPointElementName(GestureName, i);

					//create it
					Ellipse newPoint = new Ellipse();
					newPoint.Name = pointElementName;
					newPoint.Stroke = System.Windows.Media.Brushes.Blue;
					newPoint.SetValue(Canvas.LeftProperty, (double)scaledVector.X);
					newPoint.SetValue(Canvas.TopProperty, (double)scaledVector.Y);
					newPoint.StrokeThickness = 2;
					newPoint.Width = 2;
					newPoint.Height = 2;

					if (i == highlightNodeNum)
					{
						newPoint.StrokeThickness = 4;
						newPoint.Width = 4;
						newPoint.Height = 4;
						newPoint.Stroke = System.Windows.Media.Brushes.Crimson;
					}
					parentCanvas.Children.Add(newPoint);

					i++;
				}
			}
		}


		/// <summary>
		/// Clears all of the rendered dots off the canvas for a clean start.
		/// Used by the Gesture Editor
		/// </summary>
		/// <param name="parentCanvas"></param>
		public void ClearRenderedPoints(Canvas parentCanvas)
		{
			int i = 0;
			if (_positionHistory.Count > 0)
			{
				foreach (Nui.Vector positionItemNode in _positionHistory)
				{
					FrameworkElement element = GetChildElement(GetPointElementName(GestureName, i), parentCanvas);
					if (element != null)
					{
						parentCanvas.Children.Remove(element);
					}
					i++;
				}
			}
		}

		#region Methods to find Line Elements


		/// <summary>
		/// gets the Line element name for rendering a gesture trail on a Canvas
		/// </summary>
		/// <param name="name"></param>
		/// <param name="segmentNo"></param>
		/// <returns></returns>
		private string GetLineElementName(string name, int segmentNo)
		{
			return string.Format("{0}JointLine{1}", name, segmentNo);
		}

		/// <summary>
		/// gets the Point element name for rendering a gesture trail on a Canvas
		/// </summary>
		/// <param name="name"></param>
		/// <param name="segmentNo"></param>
		/// <returns></returns>
		private string GetPointElementName(string name, int segmentNo)
		{
			return string.Format("{0}JointPoint{1}", name, segmentNo);
		}

		/// <summary>
		/// gets a named element from a Canvas
		/// </summary>
		/// <param name="elementName"></param>
		/// <param name="parentCanvas"></param>
		/// <returns></returns>
		private FrameworkElement GetChildElement(string elementName, Canvas parentCanvas)
		{
			foreach (FrameworkElement item in parentCanvas.Children)
			{
				if (item.Name == elementName)
				{
					return item;
				}
			}
			return null;
		}

		#endregion

		#endregion
	}
}

